Android13适配(API33 AndroidT)

image.png

针对所有 Android13 上运行的 APP

针对运行在 Android13 手机上的所有 APP 的行为变更(忽略 targetSdkVersion

前台服务 (FGS) 任务管理器

Android13 还新增了前台服务(FGS)任务管理器功能。
如下图,用户可以在下拉的通知栏中直接关闭前台服务和应用程序:|600
此外,如果系统检测到应用长时间运行某项前台服务(在 24 小时的时间段内至少运行 20 小时),便会向用户发送提醒通知,通知内容如下:

APP is running in the background for a long time. Tap to review.

值得注意的是,满足以下任一条件的情况下,系统均将不会显示该通知:

**注意:**如果系统针对某应用已经显示过此通知,那至少在 30 天后系统才会再次显示该通知。另外,系统级应用、安全应用(比如具有 android.app.role.EMERGENCY 角色的应用)等运行的前台服务,将不会显示在 FGS 任务管理器中。

intent 过滤器会屏蔽不匹配的 intent

当您的应用向以 Android 13 或更高版本为目标平台的其他应用的导出组件发送 intent 时,仅当该 intent 与接收应用中的 <intent-filter> 元素匹配时,系统才会传送该 intent。换言之,系统会屏蔽所有不匹配的 intent,但以下情况除外:

如果接收方应用升级到 Android 13 或更高版本,仅当 intent 与其声明的 元素匹配时,源自外部应用的所有 intent 才会传送到导出组件,而不考虑发送应用的目标 SDK 版本。
所以我们需要检查应用内是否有通过 Intent 方式启动其他 App 或发送广播,同时检查 action、data 等信息是否准确

对新安装的应用的影响

在 Android13 的设备的 APP,通知权限默认关闭;除非你动态申请通知权限且授权后才能发送通知

media sessions 的 notification 和 self-manage phone calls 的 APP 不受这个变更的影响

targetSdkVersion 小于33

通知权限默认关闭,自动弹出授权弹窗

  1. 当 App 使用通知栏功能时,系统将自动弹出授权弹窗

image.png|400
用户点击 " 允许 ",App 可正常给用户推送消息

除了 " 允许 " 和 " 不允许 " 两种选择外,用户还可以划走权限申请对话框(User swipes away from dialog),即用户未选择授权(也未选择不授权)。那么下次 App 进行通知栏消息推送时,系统将再次弹出用户授权弹窗。

targetSdkVersion 大于等于33

  1. 开发者需要在 AndroidManifest.xml 中声明 POST_NOTIFICATIONS 权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.gt.demo.mubai.push">
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest>
  1. 还需要在使用通知栏推送功能时在代码中动态申请运行时权限:
// 动态申请POST_NOTIFICATIONS权限 >= android 13
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
    val checkPermission = ContextCompat.checkSelfPermission(this@MainTabsActivity, Manifest.permission.POST_NOTIFICATIONS)
    if (checkPermission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this@MainTabsActivity, arrayOf<String>(
            Manifest.permission.POST_NOTIFICATIONS), REQUEST_PUSH_PERMISSIONS)
    }
}

// 通知权限判断
fun isNotificationEnabled(context: Context): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        try {
            // 所有通道都关闭检测为通知关闭,只要有一个通道开启,就默认为开启了通知
            if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
                val channels =
                    (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notificationChannels
                if (channels.isEmpty()) {
                    return true
                }
                channels.forEach {
                    if (it.importance != NotificationManager.IMPORTANCE_NONE) {
                        return true
                    }
                }
            }
            false
        } catch (e: Exception) {
            try {
                NotificationManagerCompat.from(context).areNotificationsEnabled()
            } catch (ignore: Exception) {
                true
            }
        }
    } else {
        try {
            NotificationManagerCompat.from(context).areNotificationsEnabled()
        } catch (ignore: Exception) {
            true
        }
    }
}
  1. 授权弹窗一旦被用户拒绝授权,下次系统将不会再出现权限申请的弹窗
  2. 如果 App 仍然要推送重要消息(比如重大版本更新)给用户,则需要引导用户前往设置界面打开通知权限。代码如下:
private void jumpNotificationSetting() {
    final ApplicationInfo applicationInfo = getApplicationInfo()
    try {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
        intent.putExtra("app_package", applicationInfo.packageName);
        intent.putExtra("android.provider.extra.APP_PACKAGE", applicationInfo.packageName);
        intent.putExtra("app_uid", applicationInfo.uid);
        startActivity(intent);
    } catch (Throwable t) {
        t.printStackTrace();
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        intent.setData(Uri.fromParts("package", applicationInfo.packageName, null));
        startActivity(intent);
    }
}

实测发现,在 Android13 的手机(Pixel 6),targetSdkVersion>=33 的 APP,不动态申请通知权限,也是会弹出这个弹窗

关闭通知权限 App 进程杀死?

Android13 的手机,在设置页,通知权限从开到关闭,App 进程会重启
问题现象:ROMWE APP 开启通知后关闭通知,返回 romwe 时出现几秒页面空白
分析:
跳转到通知权限的设置页面,通知权限从打开到关闭,App 进程重启了

对现有应用更新的影响

升级到 Android13 的设备,之前有通知权限的 APP,自动授予通知权限,而不用动态去申请通知权限

targetSdkVersion=33

只针对 targetSdkVersion=33 且运行在 Android13 手机上的 APP 的变更

可空变成了非可控

Android 13 上这里的代码多了一个@NonNull 的注解

如果适配过程中有类似问题,不要盲目删除 "?"。需要注意一下低版本的运行情况。注意 checkNotNullParameter 的校验。

AnimatorListener

Animator.AnimatorListener 变动,变成了非空
image.png

onViewDetachedFromWindow

override fun onViewDetachedFromWindow(v: View?) ,v 也是非空的了

GestureDetector

GestureDetector.OnGestureListener 接口报错
onScroll 方法的 MotionEvent 参数不会为空。原因是 Android 13 上这里的代码多了一个 @NonNull 的注解。
本以为删除可空的 "?" 就正常了,但发现在低版本手机上会报错:

Fatal Exception: java.lang.NullPointerException:
Parameter specified as non-null is null: method android.view.GestureDetector.onTouchEvent, parameter e1

因为低版本手机上,这里拿到的 MotionEvent 为空。但是在 kotlin 中,变量不可空时,它会使用 checkNotNullParameter 方法校验,如果为 null 就会发生上面的异常。
image.png
所以如果你是用 Java 实现的,不会存在这个问题。那么解决方法之一就是改用 java 实现。或者可以加上 @Suppress("NOTHING_TO_OVERRIDE", "ACCIDENTAL_OVERRIDE") 这样就不会生成 checkNotNullParameter 校验代码。

GestureDetector.SimpleOnGestureListener 的 MotionEvent�变成了非空
image.png

View.AccessibilityDelegate�

image.png

Parcelable�

image.png

更细分的媒体权限

<manifest>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
  
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
			android:maxSdkVersion="32" />
</manifest>

代码部分可以做个判断,例如:

String requestPermission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermission = Manifest.permission.READ_MEDIA_IMAGES;
} else {
    requestPermission = Manifest.permission.READ_EXTERNAL_STORAGE;
}

另外文档建议,如果你的应用只需要访问图片、照片和视频,可以考虑使用 照片选择器,而不是声明 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 权限。

WiFi 权限变更

在以往版本的 Android 系统下,如果 App 要使用 WiFi 相关功能,需要申请 ACCESS_FINE_LOCATION,即位置权限,如下图:
image.png|600
为了避免 App 过度索权,更好地保护终端用户隐私,Android13 将 WiFi 权限从位置权限中分离了出来,引入了新的运行时权限:NEARBY_WIFI_DEVICES
如果 App 仅需要使用 WiFi 相关的 API,并不需要使用 getScanResults()、startScan() 等与位置相关的 API,那么建议 App 开发者切换到新的 NEARBY_WIFI_DEVICES 权限。
新的 WiFi 权限运行机制:
image.png|600
如果你的应用不会通过 Wi-Fi API 推导物理位置,请将 usesPermissionFlags 属性设为 neverForLocation。同时将 ACCESS_FINE_LOCATION 权限的最高 SDK 版本设置为 32
如果你使用了 WifiManager 的 getScanResults() 或 startScan(),在 Android 13 还是需要 ACCESS_FINE_LOCATION 权限,所以去除 android:maxSdkVersion="32"

<manifest ...>
  <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"       
			android:usesPermissionFlags="neverForLocation"/>
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
			android:maxSdkVersion="32" />
</manifest>

需要 NEARBY_WIFI_DEVICES 权限的 API 方法:

系统 API 变更�

API 删除

 public static PackageInfo getPackageInfoCompat(PackageManager packageManager, String packageName, int flag)
        throws PackageManager.NameNotFoundException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        return packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flag));
    } else {
        return packageManager.getPackageInfo(packageName, flag);
    }
}
public static <T extends Parcelable> T getParcelableExtraCompat(Intent intent, String name, Class<T> clazz) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        return intent.getParcelableExtra(name, clazz);
    } else {
        return intent.getParcelableExtra(name);
    }
}

WebView 现在始终会根据应用的主题属性 isLightTheme 来设置媒体查询 prefers-color-scheme。换句话说,如果 isLightTheme 为 true 或未指定,则 prefers-color-scheme 为 light;否则为 dark。此行为意味着,系统会自动应用 Web 内容的浅色或深色样式(如果相应内容支持应用主题)。
对于大多数应用,新行为应自动应用适当的应用样式,不过,您应测试应用以检查是否存在可能已手动控制深色模式设置的情况。
如果您仍需要自定义应用的颜色主题行为,请改用 setAlgorithmicDarkeningAllowed() 方法。为了向后兼容以前的 Android 版本,我们建议使用 AndroidX 中的等效 setAlgorithmicDarkeningAllowed() 方法。

广播和 intent 更新相关

系统广播不受 RECEIVER_NOT_EXPORTED 影响。

精确的闹钟权限

为了节省系统资源,Android12 引入了 SCHEDULE_EXACT_ALARM 权限进行 " 闹钟和提醒 " 功能的授权管理。Android13 则又引入了新的闹钟权限 USE_EXACT_ALARM
和 Android12 的 SCHEDULE_EXACT_ALARM 权限不同,如果 App 已经申请使用了 USE_EXACT_ALARM 新权限,那么用户是不能在设置页面里关闭授权的。
对于日程管理、时间管理等类型的 App 来讲,Android13 引入的 USE_EXACT_ALARM 权限能够带来一定便利。相比 Android12 的 SCHEDULE_EXACT_ALARM 权限,使用新权限的应用将不再需要频繁打扰用户进行授权,能够更高效地为用户提供闹钟、日程提醒等服务。
不过,为了防止新权限被滥用,GooglePlay 设置了严格的上架审核机制。开发者要注意,一旦使用了 USE_EXACT_ALARM 权限,App 在上架 GooglePlay 时将会被平台严格审查。除非 App 属于闹钟、计时器、日历等类型的应用或者在已被列入到应用市场的白名单里,否则 GooglePlay 将不会允许使用该权限的应用上架。

后台的传感器权限

App 在后台运行时,如果需要获取心率、体温、血氧饱和度等传感器信息,将不仅需要向用户申请现有的 BODY_SENSORS 权限,还必须声明新的 BODY_SENSORS_BACKGROUND 权限。

使用 Google Play 服务广告 ID 且以 Android 13(API 级别 33)及更高版本为目标平台的应用必须在其清单文件中声明常规 AD_ID 权限,如下所示:

<manifest ...>
    <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>

    <application ...>
        ...
    </application>
</manifest>

如果您的应用以 Android 13 或更高版本为目标平台且未声明此权限,系统会自动移除广告 ID 并将其替换为一串零。

Android13 新特性

一直以来,剪切板功能存在这样一个隐患,即剪切板复制的内容中可能存在敏感信息。为了更好地保障剪切板中的隐私内容(比如手机号码、邮箱、账号密码等)不被泄露,Android13 对剪切板功能进行了更新。
Android13 剪切板功能的使用分 2 步:

  1. 确认内容已成功复制
  2. 提供所复制内容的预览。

|600
Android13 还提供了脱敏功能,使用户能够对剪切板中的敏感信息进行隐藏,实现了便利性和安全性兼得。

Android 13 允许您指定您应用中的特定广播接收器是否应 exported 以及是否对设备上的其他应用可见。如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。
以 Android 13 或更高版本为目标平台的应用,必须为每个广播接收器指定 RECEIVER_EXPORTEDRECEIVER_NOT_EXPORTED。否则,当您尝试注册广播接收器时,系统会抛出 SecurityException。

Caused by: java.lang.SecurityException: com.xxx.xxx: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts

当然这个目前不是强制适配的,需要在开发者选项 -> 应用兼容变更中开启 DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED。这个安全增强措施默认是关闭的,所以暂时无影响。
适配:

// 这个广播接收器能够接收来自其他应用程序的广播。
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
                    RECEIVER_EXPORTED);
// 这个私有广播接收器不能够接收来自其他应用的广播。
context.registerReceiver(privateBroadcastReceiver, intentFilter,
                    RECEIVER_NOT_EXPORTED);

将普通广播替换为了本地广播(LocalBroadcastManager),不受影响

Android 13(API 级别 33)针对手机、大屏设备和可折叠设备等 Android 设备引入了预测性返回手势。该功能的发布历程跨度将达多年;完全实现后,该功能可让用户在完全完成某个返回手势之前就能预览此手势完成后的目的地或其他结果,以便用户能够决定是继续完成手势还是留在当前视图中。
bbe6cd03580444ccb08c6314b298648d.gif|400
参照文档 添加对预测性返回手势的支持 以及运行官方 Codelab 发现在 Vivo、OPPO、小米上都不起作用,可能被阉割了。。。使用模拟器正常。目前此功能在开发者选项中供测试使用。官方计划在未来的 Android 版本中向用户提供此界面。

在许多情况下,多语言用户会将其系统语言设置为某一种语言(例如英语),但又想为特定应用选择其他语言(例如荷兰语、中文或印地语)。为了帮助应用为这些用户提供更好的体验,Android 13 针对支持多种语言的应用引入了以下功能:

  1. 系统设置:用户可以在这个集中位置为每个应用选择首选语言

您的应用必须在应用的清单中声明 android:localeConfig 属性,以告知系统它支持多种语言。

  1. 其他 API:借助这些公共 API(例如 LocaleManager 中的 setApplicationLocales() 和 getApplicationLocales() 方法),应用可以在运行时设置不同于系统语言的其他语言。

这些 API 会自动与系统设置同步;因此,使用这些 API 创建自定义应用内语言选择器的应用将确保用户获得一致的用户体验,无论他们在何处选择语言偏好设置。公共 API 还有助于减少样板代码量、支持拆分 APK,并且支持应用自动备份,以存储应用级的用户语言设置。

几部国产手机此功能都被阉割
image.png|400

更快断字

断字让分行的文本更易于阅读,并且有助于使界面更具自适应性。从 Android 13 开始,断字性能提升了高达 200%,因此您可以在 TextView 中启用更快断字功能,而几乎不会影响渲染性能。如需启用更快断字功能,请在 setHyphenationFrequency() 中使用 fullFast 或 normalFast 频率。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    // 适用于聊天消息
    textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL_FAST);
    // 标准连词符
    textView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST)
}

用 fullFast 属性前后对比:
使用前|400
使用后|400

带主题的应用图标

从 Android 13 开始,您可以选择启用带主题的应用图标。借助此功能,用户可以调节受支持的 Android 应用图标色调,以继承所选壁纸和其他主题的配色。
如需支持此功能,您的应用必须提供 自适应图标 和单色应用图标,并通过清单中的 <adaptive-icon> 元素指向该单色应用图标。如果用户启用了带主题的应用图标(换句话说,在系统设置中开启了带主题的图标切换开关),而启动器支持此功能,则系统将使用用户选择的壁纸和主题来确定色调颜色,然后该颜色将应用于单色应用图标。
|400
适配方法很简单,只需要额外添加 <monochrome/> 单色应用图标配置就可以支持这个功能:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@mipmap/ic_launcher_background"/>
    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
    <monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

测试了几个部手机都不支持此功能,所以只能使用模拟器测试效果。模拟器效果图:
image.png|400

其他

kt 升级相关(升级 1.8,涉及 kotlin-android-extensions 插件删除导致大量语法错误,可后续二期升级)

语法相关

kae 插件删除

image.png
image.png

        - 可使用viewbinding替换kotlinx.android.synthetic,一些提效手段参考[升级kotlin1.8快速切换Synthetic到Viewbinding相关方案调研](https://wiki.dotfashion.cn/pages/viewpage.action?pageId=1145126559)
  - falcon相关插件需要做适配(有编译问题,目前关闭isEnableFalconPlugin开关绕过,待后续完善)

AndroidX 组件升级 (待补充)

OkHttp 升级(待补充)

Firebase 升级(待补充)